#include "stdafx.h"
#include <vector>
#include <string>
#include <algorithm>

#include "DebugData.h"
#include "MyException.h"
#include "8085Doc.h"
#include "8085View.h"
#include "MyTypedef.h"
#include "constants.h"
#include "GlobalFunctions.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

using namespace std;

///////////////////////////////////////////////////////////////////////////
//
typedef pair< string, string > AsmKeyword;

typedef vector< AsmKeyword > strings;

string make_string( const char * str ) { return str ? string( str ) : string( ); }

///////////////////////////////////////////////////////////////////////////
//
class Match {
	public:
		Match( const AsmKeyword& str ) : matched_keyword( &str )	{ length = pos = 0; }
		Match( const AsmKeyword* str ) : matched_keyword( str )	{ length = pos = 0; }

		const AsmKeyword*	get_matched_keyword( )	{ return matched_keyword; }
		bool				did_match( )			{ return !!match_length( ); } 

		// length of the matched string 
		int&				match_length( )	{ return length;	}
		int&				match_pos( )	{ return pos;		}

	private:
		int					pos;			// position in the buffer where the string was found.
		const AsmKeyword*	matched_keyword;
		int					length;
};


///////////////////////////////////////////////////////////////////////////
//
class AsmMatch : public Match {
public:
	AsmMatch( const AsmKeyword& str ) : Match( &str )	{ init( ); }
	AsmMatch( const AsmKeyword* str ) : Match( str )	{ init( ); }
	ColorIndices&	get_color_index( ) { return color_index; }
	bool			continue_matching;
	int&			get_token_id( )		{ return token_id; }
	
private:
	int				token_id;
	void init( ) { 
		continue_matching = true; 
		color_index		= COLORINDEX_NORMALTEXT; 
		token_id		=	EOLN;
	}
	ColorIndices	color_index;	// the enum value which represents the color index
};

///////////////////////////////////////////////////////////////////////////
//
class StringSet {
public:
	void insert( const char* str1, const char* str2 = 0 ) { 	// populate the strings
		vec.push_back( make_pair( make_string( str1 ), make_string( str2 ) ) ); 
	}

	bool exact_match_second( const string& str, int pos1, const string& other, int pos2 = 0 )
	{
		int size1 = str.size( );
		int size2 = other.size( );
		int i=0;
		int j=0;
		for ( i = pos1, j = pos2; i< size1 && j<size2 ; i++, j++ ) {
			if ( tolower( str[i] ) != tolower( other[j] ) )
				return false;
		}
		return ( i-pos1 ==  size2 ); // the strings must have matched exactly
	}

	AsmMatch contains( const string& candidate, int from_index ) {	
		// check to see if the vector contains the string
		int size;
		for( 
			strings::iterator it = vec.begin( ),
			last = vec.end( ); 
			it != last ;
			it++ ) {
				const AsmKeyword& key	= (*it);
				const string&	other	= key.first;
				size					= other.size( );

				if ( exact_match_second( candidate, from_index, other )  ) {
					// now the next byte in the candidate should not be a
					// alpha-numeric or there should be no character at all
					if ( (candidate.size( ) > size + from_index ) && 
						isalnum( candidate[size + from_index] ) ) {
						continue;
					}
					AsmMatch m( &(*it ) );
					m.match_pos( ) = from_index;
					m.match_length( ) = size;
					return m;
				}
		} // for
		return AsmMatch( 0 );
	}

private:
	strings vec;
};  // StringSet


class AsmStringSet : public StringSet {
public:
	AsmMatch contains( const string& candidate, int from_index ) {	
		AsmMatch m = StringSet::contains( candidate, from_index );
		const AsmKeyword* got = m.get_matched_keyword( );
		if ( got ) {
			m.get_color_index( )	= color_index;
			m.get_token_id( )		= token_id;
		}
		return m;
	};

	int				token_id;
	ColorIndices	color_index;	// the enum value which represents the color index
}; // AsmStringSet

///////////////////////////////////////////////////////////////////////////
//
class AsmLanguageStrings {
public:
	virtual ColorIndices	get_color_index( ) = 0;
};

class AsmDirectives : public AsmLanguageStrings {
public:
	virtual ColorIndices	get_color_index( ) {  return set_.color_index; }
	AsmDirectives( ) {
		set_.color_index = COLORINDEX_PREPROCESSOR;
		set_.token_id	 = DIRECTIVE;
		set_.insert(".setword", " Directive : .setword <addr>, <word>:  Assign the memory address with a value" );
		set_.insert(".setbyte", " Directive : .setbyte <addr>, <byte>:  Assign the memory address with a value" );
		set_.insert(".equ"	  ,	" Directive : .equ <identifier>, <value>:  Assign a value to an identifier" );
		set_.insert(".begin"  ,	" Directive : .begin <word>:  Set the IP to this address" );
		set_.insert(".org"	  ,	" Directive : .org <word>:  The next instruction is placed at this address" );
		set_.insert(".fill"	  ,	" Directive : .fill <word>, <word>, <byte>:  Fill the memory range with this value" );
	};

	AsmMatch contains( const string& str, int i ) { return set_.contains( str, i ); }
private:
	AsmStringSet set_;
};

static AsmDirectives directive_set;


///////////////////////////////////////////////////////////////////////////
//
class AsmRegisters : public AsmLanguageStrings {
public:
	virtual ColorIndices	get_color_index( ) {  return set_.color_index; }
	AsmRegisters( ) {
		set_.color_index = COLORINDEX_NUMBER;
		set_.token_id	 = REGISTER;
		set_.insert("a");
		set_.insert("b");
		set_.insert("c");
		set_.insert("d");
		set_.insert("e");
		set_.insert("h");
		set_.insert("l");
		set_.insert("m");
		set_.insert("sp");
		set_.insert("psw");
	};

	AsmMatch contains( const string& str, int i ) { return set_.contains( str, i ); }
private:
	AsmStringSet set_;
};

static AsmRegisters register_set;

///////////////////////////////////////////////////////////////////////////
//
class AsmKeywords : public AsmLanguageStrings {
public:
	virtual ColorIndices	get_color_index( ) {  return set_.color_index; }
	AsmKeywords( ) {
		set_.color_index = COLORINDEX_KEYWORD;
		set_.token_id	 = INSTRUCTION;
		set_.insert( "aci"	, "  ACI <8 bit> : Add immediate accumulator with carry :  The previous carry flat is cleared after addition" );
		set_.insert( "adc"	, "  ADC R/M : Add register to accumulator with carry  : All flags are modified to reflect the result of addition" );
		set_.insert( "add"	, "  ADD R/M : Add reg/mem to accumulator : All flags are modified to reflect the result of addition" );
		set_.insert( "adi"	, "  ACI <8 bit> :Add immediate to accumulator : All flags are modified to reflect the result of addition" );
		set_.insert( "ana"	, "  ANA R/M : Logical And with accumulator : S,Z,P are modified to reflect the result of operation.  CY is reset and AC is set." );
		set_.insert( "ani"	, "  ANI <8 bit > : Logical And immediate with accumulator : S,Z,P are modified to reflect the result of operation.  CY is reset and AC is set." );
		set_.insert( "call"	, "  CALL <16 bit addr> : Branch to subroutine :  IP is pushed to stack before the call.  Note:- call 05 will make System Calls " );
		set_.insert( "cc"	, "  CC <16 bit addr> : Call on carry :  IP is pushed to stack before the call.  Note:- call 05 will make System Calls " );
		set_.insert( "cm"	, "  CC <16 bit addr> : Call on minus :  IP is pushed to stack before the call.  Note:- call 05 will make System Calls " );
		set_.insert( "cma"	, "  CMA : Complement accumulator :  No flags are affected" );
		set_.insert( "cmc"	, "  CMC : Complement carry :  No other flags are affected" );
		set_.insert( "cmp"	, "  CMP R/M : Compare with accumulator" );
		set_.insert( "cnc"	, "  CNC <16 bit addr>  :  Call on no carry : NOTE :- addres must be specified in reverse order" );
		set_.insert( "cnz"	, "  CNZ <16 bit addr>  :  Call if not zero : NOTE :- addres must be specified in reverse order" );
		set_.insert( "cp"	, "  CP <16 bit addr>  :  Call on plus : NOTE :- addres must be specified in reverse order" );
		set_.insert( "cpe"	, "  CPE <16 bit addr>  :  Call on parity even : NOTE :- addres must be specified in reverse order" );
		set_.insert( "cpi"	, "  CPI <8 bit data>  :  Compare immediate  :  S, P, Ac are modified in addition to Z and C" );
		set_.insert( "cpo"	, "  CPO <16 bit addr>  :  Call on parity odd : NOTE :- addres must be specified in reverse order" );
		set_.insert( "cz"	, "  CZ <16 bit addr>  :  Call on zero : NOTE :- addres must be specified in reverse order" );
		set_.insert( "daa"	, "  DAA :  Decimal adjust accumulator:  S, Z, Ac, P, Cy flats are altered to reflect the operation results" );
		set_.insert( "dad"	, "DAD Reg Pair(HL,SP) : Add register pair to H and L registers:  If result is > 16 bits, the CY flag is set. No other flags affected");
		set_.insert( "dcr"	, "  DCR R/M : Decrement reg/mem by 1:  S, Z, P, AC are modified.  CY is not modified" );
		set_.insert( "dcx"	, "  DCX Reg Pair : Decrement register pair by 1:  No flags are affected" );
		set_.insert( "di"	, "  DI  :  Disable interrupts :  All interrupts except TRAP are disabled." );
		set_.insert( "ei"	, "  EI  :  Enable interrupts :  All interrupts are enabled." );
		set_.insert( "hlt"	, "  HLT  :  Halt and enter wait state.  A message box is displayed and simulation halts" );
		set_.insert( "in"	, "  IN <8 bit port addr >  :  Input data to accumulator from port:  No flags are affected" );
		set_.insert( "inr"	, "  INR R/M :  Increment contents of reg/mem by 1:  S, Z, P, AC are modified.  CY is not modified" );
		set_.insert( "inx"	, "  INX Reg Pair : Increment register pair by 1:  No flags are affected" );
		set_.insert( "jc"	, "  JC <16 bit addr>  :  Jump on carry : NOTE :- addres must be specified in reverse order" );
		set_.insert( "jm"	, "  JM <16 bit addr>  :  Jump on minus : When S=1 : NOTE :- addres must be specified in reverse order" );
		set_.insert( "jmp"	, "  JMP <16 bit addr>  :  Jump unconditionally : NOTE :- addres must be specified in reverse order" );
		set_.insert( "jnc"	, "  JNC <16 bit addr>  :  Jump on no carry : NOTE :- addres must be specified in reverse order" );
		set_.insert( "jnz"	, "  JNZ <16 bit addr>  :  Jump if not zero : NOTE :- addres must be specified in reverse order" );
		set_.insert( "jp"	, "  JP <16 bit addr>  :  Jump on plus :  When S=0 :NOTE :- addres must be specified in reverse order" );
		set_.insert( "jpe"	, "  JPE <16 bit addr>  :  Jump on parity even : NOTE :- addres must be specified in reverse order" );
		set_.insert( "jpo"	, "  JPO <16 bit addr>  :  Jump on parity odd : NOTE :- addres must be specified in reverse order" );
		set_.insert( "jz"	, "  JZ <16 bit addr>  :  Jump on zero : NOTE :- addres must be specified in reverse order" );
		set_.insert( "lda"	, "  LDA <16 bit address>  :  Load accumulator direct:  Load acc with contents of memory loc specified" );
		set_.insert( "ldax"	, "  LDAX B/D registers :  Load accumulator indirect :  Load acc with the contets of mem loca specified in reg" );
		set_.insert( "lhld"	, "  LHLD <16 bit address>  :  Load H and L registers direct" );
		set_.insert( "lxi"	, "  LXI <16 bit data>  :  Load register pair immediate with the data specified" );
		set_.insert( "mov"	, "  MOV R/M , R/M  :  Copy from source to destination : Note: one of the operands has to he a reg." );
		set_.insert( "mvi"	, "  MVI R/M, <8 bit data>  :  Move immediate 8-bit to the reg." );
		set_.insert( "nop"	, "  NOP  :  No operation  : No flags are affected" );
		set_.insert( "ora"	, "  ORA R/M : Logically OR with accumulator : S,Z,P are modified. AC and CY are reset." );
		set_.insert( "ori"	, "  ORI <8 bit > : Logically OR immediate :  S,Z,P are modified. AC and CY are reset." );
		set_.insert( "out"	, "  OUT <8 bit port address> : Output data from acc to a the port specified." );
		set_.insert( "pchl"	, "  PCHL : Load program counter with contents of HL." );
		set_.insert( "pop"	, "  POP Reg-Pair : Pop off stack to register pair : POP H, POP SP, POP PSW are valid." );
		set_.insert( "push"	, "  PUSH Reg-Pair : Push register pair onto stack :  PUSH H, PUSH SP, PUSH PSW are valid." );
		set_.insert( "ral"	, "  RAL : Rotate acc left through carry :  CY is modified according to bit D7. S,Z,AC,P are unchanged." );
		set_.insert( "rar"	, "  RAR : Rotate acc right through carry :  CY is modified according to bit D0. S,Z,AC,P are unchanged." );
		set_.insert( "rc"	, "  RC  : Return on Carry." );
		set_.insert( "ret"	, "  RET : Return from subroutine unconditionally." );
		set_.insert( "rim"	, "  RIM  : Read interrupt Mask into accumulator  :  NOT IMPLEMENTED" );
		set_.insert( "rlc"	, "  RLC : Rotate accumulator left. Bit D7 is places in D0 and the carry flag. Other flags are unchanged." );
		set_.insert( "rm"	, "  RM  : Return on minus :  When S=1." );
		set_.insert( "rnc"	, "  RNC : Return on no Carry." );
		set_.insert( "rnz"	, "  RNZ : Return on no Zero." );
		set_.insert( "rp"	, "  RP  : Return on Positive:  When S=0." );
		set_.insert( "rpe"	, "  RPE : Return on Parity Even :  When P=1." );
		set_.insert( "rpo"	, "  RPO : Return on Parity Odd :  When P=0." );
		set_.insert( "rrc"	, "  RRC : Rotate Accumulator right :  Bit D0 is places in D7 and the carry flag. Other flags are unchanged." );
		set_.insert( "rst"	, "  RST : Restart Instruction : RST 0 - 7 correspond to CALL instructions to 0,8,10,18,20,28,30,38 addr loc." );
		set_.insert( "rz"	, "  RZ  : Return on Zero :  When Z=1." );
		set_.insert( "sbb"	, "  SUB R/M :  Subtract source and borrow from accumulator : All flags are modified to reflect the result of the operation" );
		set_.insert( "sbi"	, "  SBI <8 bit> :  Subtract immediate with borrow : All flags are modified to reflect the result of the operation" );
		set_.insert( "shld"	, "  SHLD <16 bit addr> : Store H, L direct :  Contents of L, H are stored in mem loc \'addr\' in that order.  L is stored first." );
		set_.insert( "sim"	, "  SIM  :  Set interrupt mask :  NOT IMPLEMENTED" );
		set_.insert( "sphl"	, "  SPHL :  Copy H and L reg to stack pointer.  H is copied to high order and L to low order." );
		set_.insert( "sta"	, "  STA <16 bit addr> :  Store accumulator direct.  Contents of acc are copied to 16 bit addr." );
		set_.insert( "stax"	, "  STAX B/D registers: Store accumulator indirect:  The contents of acc are copied to mem loc specified by the regs" );
		set_.insert( "stc"	, "  STC  :  Set Carry :  No other flags are affected." );
		set_.insert( "sub"	, "  SUB R/M :  Subtract reg/mem from accumulator : All flags are modified to reflect the result of the operation" );
		set_.insert( "sui"	, "  SUI <8 bit> :  Subtract immediate from accumulator : All flags are modified to reflect the result of the operation" );
		set_.insert( "xchg"	, "  XCHG :  Exchange H and L with D and E :  H is copied to D and L to E." );
		set_.insert( "xra"	, "  XRA  R/M :  Exclusive OR with acc :  Z, S, P are modifed.  AC and CY are reset." );
		set_.insert( "xri"	, "  XRI <8-bit data> :  Exclusive OR immediate with acc :  Z, S, P are modifed.  AC and CY are reset." );
		set_.insert( "xthl"	, "  XTHL  :  Exchange H and L with top of stack :  L is exchanged with [SP] and H is exchanged with [SP+1]. SP itself is not changed." );
	};

	AsmMatch contains( const string& str, int i ) { return set_.contains( str, i ); }
private:
	AsmStringSet set_;
};

static AsmKeywords	keyword_set;

///////////////////////////////////////////////////////////////////////////
//
// enhancements to StringSet specific to 8085 assembly language.
class Asm8085Language {
public:
	AsmMatch	parse_line( const string& buf, int pos = 0 );
	AsmMatch	is_an_instruction( const string& buf, int  i=0 )  {	return keyword_set.contains( buf, i ); }
	AsmMatch	is_asm_directive( const string& buf, int i=0 ) { 
		return directive_set.contains( buf, i ); 
	}
	AsmMatch	is_a_register( const string& buf, int  i=0 )  {	return register_set.contains( buf, i ); }
	const string*	get_asm_help_text( const string& );
private:
	AsmKeywords		keywords;
	AsmDirectives	directives;
};

AsmMatch
Asm8085Language::parse_line( const string& buf, int pos )
{
	int len	= buf.size( );
	int i = pos;
	//for ( int i=pos ; i<( pos + len ) ; i++ ) {
		// skip spaces
		i = skip_space( buf, i );

		if ( buf[i] ==';' ) {
			// we hit a comment, so paint the rest of the line as comment
			AsmMatch m(0);
			m.match_pos( )			= i;
			m.match_length( )		= len - i;
			m.continue_matching		= false;
			m.get_color_index( )	= COLORINDEX_COMMENT;
			m.get_token_id( )		= COMMENT;
			return m;
		}

		if ( buf[i] == '.' ) {
			// then this is a directive
			AsmMatch m = is_asm_directive( buf, i );
			if ( ! m.did_match( ) ) {
				// this means the directive was wrong.
				m.get_color_index( ) = COLORINDEX_ERRORTEXT;
			}
			m.match_pos( ) = i;
			m.match_length( ) = len - i;
			m.continue_matching	= false;
			return m;
		}

		AsmMatch m = is_an_instruction( buf, i );

		if ( m.did_match( ) ) {
			m.match_pos( ) = i;
			m.continue_matching	= true;
			return m;
		}

		m	= is_a_register( buf, i );
		if ( m.did_match( ) ) {
			m.match_pos( ) = i;
			m.continue_matching	= true;
			return m;
		}

		// if it is none of these then just match a word
		m.match_pos( ) = i;
		i	= skip_word( buf, i );
		m.match_length( )  = i - m.match_pos( );
		return m;

	//} // for
}

const string*	
Asm8085Language::get_asm_help_text( const string& buf )
{
	AsmMatch m = parse_line( buf );
	const AsmKeyword* got = m.get_matched_keyword( );
	if ( got )
		return &got->second;
	return 0;
}

static Asm8085Language	the_8085_language;
///////////////////////////////////////////////////////////////////////////
//


#define DEFINE_BLOCK(pos, colorindex)	\
	ASSERT((pos) >= 0 && (pos) <= nLength);\
	if (pBuf != NULL)\
	{\
		if (nActualItems == 0 || pBuf[nActualItems - 1].m_nCharPos <= (pos)){\
		pBuf[nActualItems].m_nCharPos = (pos);\
		pBuf[nActualItems].m_nColorIndex = (colorindex);\
		nActualItems ++;}\
	}


///////////////////////////////////////////////////////////////////////////
//
const char*
CMy8085View::GetAsmHelpText( int nLineIndex )
{
	if ( ! GetDocument( )->m_bMiniHelp )
		return 0;
	int		nLength		= GetLineLength(nLineIndex);
	if (nLength <= 0)
		return 0;

	string buf( GetLineChars(nLineIndex), nLength );
	const string* str = the_8085_language.get_asm_help_text( buf );

	return str ? str->c_str( ) : 0;
}


///////////////////////////////////////////////////////////////////////////
//
DWORD CMy8085View::ParseLine(DWORD dwCookie, int nLineIndex, TEXTBLOCK *pBuf, int &nActualItems)
{
	int token_id  = EOLN;
	if ( ! GetDocument( )->m_bSyntaxHighlighting )
		return dwCookie;
	int		nLength		= GetLineLength(nLineIndex);
	if (nLength <= 0)
		return dwCookie;
	int i=0;
	int j=0;
	string buf( GetLineChars(nLineIndex), nLength );
	for ( i=0 ; i < nLength ; ) {
		AsmMatch m = the_8085_language.parse_line( buf, i );
		if ( m.did_match( ) ) {
			if ( token_id == EOLN ) 
				token_id	= m.get_token_id( );
			i = m.match_pos( );
			for( int j=i ; j < ( i + m.match_length( ) ) ; j++ ) {
				DEFINE_BLOCK(j, m.get_color_index( ) );
			}
			i += m.match_length( );
		}

		if ( !m.continue_matching )
			break;

		if ( m.match_length( ) == 0 ) {
			DEFINE_BLOCK(i, COLORINDEX_NORMALTEXT );
			i++;
			continue;
		}
	}

	for ( j = i+1; j<nLength ; j++ ) {
		DEFINE_BLOCK(j, COLORINDEX_NORMALTEXT );
	}
	return dwCookie;
}